Skip to content

Conversation

@steven10a
Copy link
Collaborator

@steven10a steven10a commented Oct 17, 2025

Updates handling of conversation history

  • Normalizes conversation history regardless of the endpoint used by the user. Logic extracted to conversation.py helper
  • Conversation history is now accessible by any check
  • Supports using previous_response_id for conversation history with responses endpoint
  • GuardrailAgent uses session if passed into Runner.run(). Otherwise treats the input as the conversation history
  • Fixed streaming to provide correct conversation history
  • Updated and created new tests

Copilot AI review requested due to automatic review settings October 17, 2025 17:13
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates conversation history handling to normalize conversation data across different endpoints and provide consistent access to conversation history for guardrails. The main goal is to ensure that conversation history is accessible by all guardrail checks regardless of which API endpoint is used.

  • Extracts conversation normalization logic to a dedicated conversation.py utility module
  • Updates GuardrailAgent to use session-based conversation history when available, falling back to input conversation
  • Fixes streaming to provide proper conversation history context during output guardrails

Reviewed Changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/guardrails/utils/conversation.py New module providing conversation normalization utilities across different API formats
src/guardrails/agents.py Refactors agent conversation handling to use sessions and normalized conversation history
src/guardrails/client.py Updates client methods to use normalized conversation handling
src/guardrails/_streaming.py Fixes streaming to include conversation history in output guardrail checks
src/guardrails/resources/responses/responses.py Updates to use normalized conversation history
src/guardrails/resources/chat/chat.py Updates to use normalized conversation history
tests/unit/test_agents.py Updates tests for new agent conversation handling patterns
tests/unit/test_client_sync.py Updates test assertions for normalized conversation format

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +19 to +29
"""Normalized representation of a conversation item.
Attributes:
role: Logical speaker role (user, assistant, system, tool, etc.).
type: Optional type discriminator for non-message items such as
``function_call`` or ``function_call_output``.
content: Primary text payload for message-like items.
tool_name: Name of the tool/function associated with the entry.
arguments: Serialized tool/function arguments when available.
output: Serialized tool result payload when available.
call_id: Identifier that links tool calls and outputs.
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConversationEntry class is missing a comprehensive docstring that should explain its purpose as a normalized conversation item and provide examples of usage.

Suggested change
"""Normalized representation of a conversation item.
Attributes:
role: Logical speaker role (user, assistant, system, tool, etc.).
type: Optional type discriminator for non-message items such as
``function_call`` or ``function_call_output``.
content: Primary text payload for message-like items.
tool_name: Name of the tool/function associated with the entry.
arguments: Serialized tool/function arguments when available.
output: Serialized tool result payload when available.
call_id: Identifier that links tool calls and outputs.
"""
Represents a normalized item in a conversation history, such as a user message,
assistant response, tool/function call, or tool output. This class provides a
consistent structure for conversation entries, regardless of the originating
API or provider, enabling guardrails to process and reason about conversation
turns in a uniform way.
Each entry may represent a message (from user, assistant, or system), a tool
call (function invocation), or a tool output (function result). The fields
capture the semantic role, type, content, and any associated tool/function
metadata.
Attributes:
role: Logical speaker role (e.g., "user", "assistant", "system", "tool").
type: Optional type discriminator for non-message items such as
"function_call" or "function_call_output".
content: Primary text payload for message-like items.
tool_name: Name of the tool/function associated with the entry.
arguments: Serialized tool/function arguments when available.
output: Serialized tool result payload when available.
call_id: Identifier that links tool calls and outputs.
Example:
>>> # User message
>>> entry = ConversationEntry(role="user", content="Hello, assistant!")
>>> entry.to_payload()
{'role': 'user', 'content': 'Hello, assistant!'}
>>> # Assistant response
>>> entry = ConversationEntry(role="assistant", content="Hello! How can I help you?")
>>> entry.to_payload()
{'role': 'assistant', 'content': 'Hello! How can I help you?'}
>>> # Tool/function call
>>> entry = ConversationEntry(
... role="assistant",
... type="function_call",
... tool_name="get_weather",
... arguments='{"location": "London"}',
... call_id="abc123"
... )
>>> entry.to_payload()
{
... 'role': 'assistant',
... 'type': 'function_call',
... 'tool_name': 'get_weather',
... 'arguments': '{"location": "London"}',
... 'call_id': 'abc123'
}

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion this is an unnecessary expansion of the current doc string. The current version is concise and matches the codebase style.

return text
return _extract_text(content.get("content"))

if isinstance(content, Sequence) and not isinstance(content, bytes | bytearray):
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union syntax bytes | bytearray requires Python 3.10+. Consider using (bytes, bytearray) for broader compatibility.

Suggested change
if isinstance(content, Sequence) and not isinstance(content, bytes | bytearray):
if isinstance(content, Sequence) and not isinstance(content, (bytes, bytearray)):

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +30
"""Stub tool context carrying name, arguments, and optional call id."""

tool_name: str
tool_arguments: dict[str, Any]
tool_arguments: dict[str, Any] | str
tool_call_id: str | None = None
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union syntax dict[str, Any] | str and str | None require Python 3.10+. Consider using Union[dict[str, Any], str] and Optional[str] for broader compatibility.

Copilot uses AI. Check for mistakes.
_user_messages: ContextVar[list[str]] = ContextVar("user_messages", default=[]) # noqa: B039
# Context variables used to expose conversation information during guardrail checks.
_agent_session: ContextVar[Any | None] = ContextVar("guardrails_agent_session", default=None)
_agent_conversation: ContextVar[tuple[dict[str, Any], ...] | None] = ContextVar(
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union syntax tuple[dict[str, Any], ...] | None requires Python 3.10+. Consider using Optional[tuple[dict[str, Any], ...]] for broader compatibility.

Copilot uses AI. Check for mistakes.
llm_stream: Any, # coroutine or async iterator of OpenAI chunks
preflight_results: list[GuardrailResult],
input_results: list[GuardrailResult],
conversation_history: list[dict[str, Any]] | None = None,
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union syntax list[dict[str, Any]] | None requires Python 3.10+. Consider using Optional[list[dict[str, Any]]] for broader compatibility.

Copilot uses AI. Check for mistakes.
llm_stream: Any, # iterator of OpenAI chunks
preflight_results: list[GuardrailResult],
input_results: list[GuardrailResult],
conversation_history: list[dict[str, Any]] | None = None,
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union syntax list[dict[str, Any]] | None requires Python 3.10+. Consider using Optional[list[dict[str, Any]]] for broader compatibility.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@gabor-openai gabor-openai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gabor-openai gabor-openai merged commit bdb0de6 into main Oct 20, 2025
3 checks passed
@gabor-openai gabor-openai deleted the dev/steven/conversation_history branch October 20, 2025 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants